Skip to content

fix: embedded omitempty field dropped / nil-pointer panic in nested embedded structs (#576, #581, #554) - #587

Open
truffle-dev wants to merge 3 commits into
goccy:masterfrom
truffle-dev:fix-issue-576-embedded-omitempty
Open

fix: embedded omitempty field dropped / nil-pointer panic in nested embedded structs (#576, #581, #554)#587
truffle-dev wants to merge 3 commits into
goccy:masterfrom
truffle-dev:fix-issue-576-embedded-omitempty

Conversation

@truffle-dev

@truffle-dev truffle-dev commented Jun 26, 2026

Copy link
Copy Markdown

Fixes #576. Also fixes the panic reported in #581 and #554.

When an embedded struct's first field is tagged omitempty and is empty, the whole containing struct serialized to {}, silently dropping every following field (#576). The same broken wiring also caused a nil-pointer panic for two closely related shapes (#581, #554).

The cause is in encoder.StructCode.ToAnonymousOpcode: prevField was set to firstField (the anonymous head opcode) rather than the last field of the embedded chain, so the omitempty field's NextField never linked to the next real field. addStructEndCode then wired that head's empty-skip jump straight to StructEnd. The non-anonymous ToOpcode path already does this correctly via lastFieldCode; this mirrors it.

// #576: dropped fields
type ErrOmit struct {
    Error string `json:"error,omitempty"`
}
type MyResult struct {
    ErrOmit
    Data string `json:"data"`
}
type Result struct{ MyResult }
// before: {}        encoding/json: {"data":""}

// #581: panic
type Reason struct {
    Code string `json:"reasonCode,omitempty"`
}
type Cov struct {
    Type string `json:"type"`
}
type Inner struct {
    Cov
    R *Reason `json:"reason,omitempty"`
}
type Outer struct{ Inner }
// before: panic (nil pointer)   encoding/json: {"type":""}

// #554: panic
type Son struct {
    B string `json:"b"`
    C string `json:"c"`
}
type Father struct {
    Son
    A []string `json:"a,omitempty"`
}
type Grandpa struct{ Father }
// before: panic (nil pointer)   encoding/json: {"b":"","c":""}

Added TestIssue576 (empty / data-only / both-fields), TestIssue581 (nil and set pointer), and TestIssue554 (double-nested embedded with trailing omitempty empty slice). Each was confirmed to fail before the change ({"data":""} -> {} for #576, panic for #581 and #554) and pass after. Full go test ./... is green, gofmt clean.

For anyone triaging the embedded-omitempty cluster: I dump-tested the neighbours against this branch. #512 is a distinct shape (a plain field followed by an embedded struct whose own field is omitempty, double-nested) that goes through a separate opcode path and still panics; not addressed here. #486 (nil embedded self-pointer emitting {"id":99,null}) and #503 (omitempty pointer child with a zero first byte) are also separate paths that survive this fix and remain open. Happy to follow those up in separate PRs.

When an embedded struct's first field has the omitempty tag and is empty,
the whole containing struct serialized to `{}`, dropping every following
field (issue goccy#576).

The cause is in encoder.StructCode.ToAnonymousOpcode: prevField was set to
firstField (the anonymous head opcode) instead of the last field of the
embedded chain, so the omitempty head's NextField never linked to the next
real field. addStructEndCode then wired that head's empty-skip jump straight
to StructEnd. Mirror the non-anonymous ToOpcode path and use lastFieldCode.
The nil-pointer panic in goccy#581 (embedded value struct followed by a nil
omitempty pointer-to-struct field, itself embedded) shares the root cause
with goccy#576: the omitempty field's NextField was never linked to the next
field in ToAnonymousOpcode. The lastFieldCode change fixes both. Add
TestIssue581 to guard the panic case.
@truffle-dev truffle-dev changed the title fix: embedded struct with omitempty first field is dropped entirely fix: embedded omitempty field dropped / nil-pointer panic in nested embedded structs (#576, #581) Jun 26, 2026
Grandpa{Father{Son{B,C}, A []string omitempty}} panics with a nil
pointer dereference on master: an embedded struct followed by an
omitempty empty-slice field, double-nested. This is the same
ToAnonymousOpcode prevField wiring bug as goccy#576/goccy#581 - the embedded
head's NextField never links to the trailing omitempty field, so the
empty-skip jump lands on a nil target. The one-line lastFieldCode fix
resolves it. Verified: panics on master, clean on this branch.
@truffle-dev truffle-dev changed the title fix: embedded omitempty field dropped / nil-pointer panic in nested embedded structs (#576, #581) fix: embedded omitempty field dropped / nil-pointer panic in nested embedded structs (#576, #581, #554) Jun 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] Embedded struct with omitempty is incorrectly omitted entirely

1 participant